package org.clockwise.srw;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.clockwise.driver.AccountOperationDriver;
import org.clockwise.driver.DataReader;
import org.clockwise.driver.LinkerDriver;
import org.clockwise.feature.Account;
import org.clockwise.multimethod.PageRank;
import org.clockwise.multimethod.SRW;
import org.clockwise.util.MyFileReader;
import org.clockwise.util.MyFileWriter;
import org.clockwise.util.MyMath;

public class UserRelationPool {

	private static HashMap<Long, HashMap<Long, double[]>> relationPair = null;

	public static String relationTarget = "RelationTarget.txt";
	public static String relationFile = "UserRelation.txt";
	public static int FEATURE_DIM = 0;

	/*
	 * dimension 1: whether there is one-way following from minId to maxId, {0,
	 * 1} dimension 2: whether there is bi-directional following between minId
	 * and maxId, {0, 1} dimension 3: the amount of followees of minId, [0, inf)
	 * dimension 4: the amount of followees of maxId, [0, inf) dimension 5: the
	 * amount of common followeees of minId and maxId, [0, inf) dimension 6: the
	 * amount of mutual friends of minId, [0, inf) dimension 7: the amount of
	 * mutual friends of maxId, [0, inf) dimension 8: the amount of common
	 * mutual friends of minId and maxId, [0, inf)
	 */

	public static void generateRelationFile() {
		List<List<List<double[]>>> data = new ArrayList<List<List<double[]>>>();
		List<List<List<Long>>> dict = new ArrayList<List<List<Long>>>();
		DataReader.readData(data, dict);
		HashMap<Long, Account> map = AccountOperationDriver.getAccount();
		generateTargetRelationFile(dict, relationTarget);

	}

	private static void loadRelation() {
		relationPair = new HashMap<Long, HashMap<Long, double[]>>();
		MyFileReader mfr = new MyFileReader(LinkerDriver.DATA_DIR
				+ relationFile);
		String temp = null;
		while ((temp = mfr.getNextLine()) != null) {
			String[] seg = temp.trim().split("\t");
			if (FEATURE_DIM == 0) {
				FEATURE_DIM = seg.length - 2;
			} else {
				assert (FEATURE_DIM == seg.length - 2);
			}

			Long minId = Long.parseLong(seg[0]);
			Long maxId = Long.parseLong(seg[1]);
			if (minId > maxId) {
				Long t = minId;
				minId = maxId;
				maxId = t;
			}
			boolean isZero = true;
			double[] w = new double[FEATURE_DIM];
			for (int i = 0; i < FEATURE_DIM; i++) {
				w[i] = Double.parseDouble(seg[i + 2]);
				if (w[i] != 0.0) {
					isZero = false;
				}
			}

			if (isZero)
				continue;

			if (relationPair.containsKey(minId)) {
				relationPair.get(minId).put(maxId, w);
			} else {
				HashMap<Long, double[]> subpair = new HashMap<Long, double[]>();
				subpair.put(maxId, w);
				relationPair.put(minId, subpair);
			}
		}
		mfr.close();
	}

	private static double[] getRelationVector(Long id1, Long id2) {
		if (relationPair == null) {
			loadRelation();
		}

		Long minId = id1 < id2 ? id1 : id2;
		Long maxId = id1 > id2 ? id1 : id2;
		if (relationPair.containsKey(minId)) {
			HashMap<Long, double[]> subpair = relationPair.get(minId);
			if (subpair.containsKey(maxId)) {
				double[] f = subpair.get(maxId);
				assert (f != null && f.length == FEATURE_DIM);
				return f;
			}
		}
		return new double[FEATURE_DIM];
	}

	public static double evaluate(double[] f, double[] omega, double eta) {
		assert (f.length == omega.length);
		return Math.exp(MyMath.product(f, omega) * eta);
	}

	private static void generateTargetRelationFile(List<List<List<Long>>> dict,
			String filename) {
		Iterator<List<List<Long>>> cuIter = dict.iterator();
		while (cuIter.hasNext()) {
			List<List<Long>> cUser = cuIter.next();

			int dim = 0;

			// the start position of each query
			List<Long> idlist = new ArrayList<Long>();
			Iterator<List<Long>> qUserIter = cUser.iterator();
			while (qUserIter.hasNext()) {
				List<Long> qUser = qUserIter.next();
				dim += qUser.size();
				Iterator<Long> userIter = qUser.iterator();
				while (userIter.hasNext()) {
					idlist.add(userIter.next());
				}
			}

			Long[] id = new Long[idlist.size()];
			idlist.toArray(id);

			// initialize the relation matrix
			double[][] matrix = new double[dim][];

			// System.out.println("dim = " + dim);

			for (int i = 0; i < dim; i++) {
				matrix[i] = new double[dim];
			}
			for (int i = 0; i < dim; i++) {
				for (int j = i + 1; j < dim; j++) {
					Long min = id[i], max = id[j];
					if (min > max) {
						Long temp = min;
						min = max;
						max = temp;
					}
					if (relationPair == null) {
						relationPair = new HashMap<Long, HashMap<Long, double[]>>();
					}
					if (relationPair.containsKey(min)) {
						HashMap<Long, double[]> subpair = relationPair.get(min);
						if (!subpair.containsKey(max)) {
							subpair.put(max, new double[FEATURE_DIM]);
						}
					} else {
						HashMap<Long, double[]> subpair = new HashMap<Long, double[]>();
						subpair.put(max, new double[FEATURE_DIM]);
						relationPair.put(min, subpair);
					}
				}
			}
		}

		MyFileWriter mfw = new MyFileWriter(LinkerDriver.DATA_DIR + filename,
				true);
		for (Long min : relationPair.keySet()) {
			HashMap<Long, double[]> subpair = relationPair.get(min);
			for (Long max : subpair.keySet()) {
				mfw.write(min + "\t" + max + "\n");
			}
		}
		mfw.close();

	}

	public static void initRelations(List<List<List<double[]>>> data,
			List<List<List<Long>>> dict, List<double[][]> ucFeature,
			List<double[][][]> uuFeature, List<int[]> ruler, List<Long[]> idList) {

		ucFeature.clear();
		uuFeature.clear();
		ruler.clear();
		idList.clear();

		if (relationPair == null) {
			loadRelation();
		}
		
		Iterator<List<List<double[]>>> cqIter = data.iterator();
		Iterator<List<List<Long>>> cuIter = dict.iterator();
		while (cqIter.hasNext() && cuIter.hasNext()) {
			List<List<double[]>> context = cqIter.next();
			List<List<Long>> cUser = cuIter.next();

			int dim = 0;

			// the start position of each query
			int[] start = new int[context.size() + 1];

			start[0] = 0;
			int q = 1;
			List<Long> idlist = new ArrayList<Long>();
			Iterator<List<double[]>> queryIter = context.iterator();
			Iterator<List<Long>> qUserIter = cUser.iterator();
			List<double[]> allFeature = new ArrayList<double[]>();
			while (queryIter.hasNext() && qUserIter.hasNext()) {
				List<double[]> query = queryIter.next();
				List<Long> qUser = qUserIter.next();
				dim += query.size();
				start[q] = start[q - 1] + query.size();
				q++;
				Iterator<double[]> featureIter = query.iterator();
				Iterator<Long> userIter = qUser.iterator();
				while (featureIter.hasNext() && userIter.hasNext()) {
					allFeature.add(featureIter.next());
					idlist.add(userIter.next());

				}
				if (featureIter.hasNext() || userIter.hasNext()) {
					System.out
							.println("Fatal Error! feature list and user list do not match!");
				}
			}

			if (queryIter.hasNext() || qUserIter.hasNext()) {
				System.out.println("Fatal Error! data and dict do not match");
			}

			ruler.add(start);
			Long[] id = new Long[idlist.size()];
			idlist.toArray(id);
			idList.add(id);
			double[][] cf = new double[allFeature.size()][];
			allFeature.toArray(cf);
			ucFeature.add(cf);

			double[][][] uuf = new double[dim][dim][FEATURE_DIM];
			for (int i = 0; i < dim; i++) {
				for (int j = 0; j < dim; j++) {
					double[] w = UserRelationPool.getRelationVector(id[i], id[j]);
					uuf[i][j] = Arrays.copyOf(w, w.length);
				}
				
				// normalize in a row (context) rather than in a segment (query). 
				// is that right?
				double[] max = new double[uuf[i][0].length];
				for (int j = 0; j < dim; j++) {
					for (int k = 0; k < max.length; k++) {
						if (max[k] < uuf[i][j][k]) {
							max[k] = uuf[i][j][k];
						}
					}
				}
				for (int j = 0; j < dim; j++) {
					for (int k = 0; k < max.length; k++) {
						if (max[k] == 0) {
							
						} else {
							uuf[i][j][k] /= max[k];
						}
					}
				}
			}
//			System.out.println(dim);
			uuFeature.add(uuf);
		}
	}

	public static void updateRelationMatrix(List<double[][][]> uuFeature,
			List<int[]> ruler, double[] omega, List<double[][]> relation,
			double eta) {
		assert (uuFeature.size() == ruler.size());
		relation.clear();
		
		Iterator<double[][][]> uufIter = uuFeature.iterator();
		Iterator<int[]> rIter = ruler.iterator();
		while (uufIter.hasNext() && rIter.hasNext()) {
			double[][][] uuf = uufIter.next();
			int[] start = rIter.next();
			assert (uuf.length == uuf[0].length);
			assert (uuf[0][0].length == omega.length);
			
			int dim = uuf.length;
			double[][] matrix = new double[dim][dim];
			for (int i = 0; i < dim; i++) {
				for (int j = 0; j < dim; j++) {
					double r = UserRelationPool.evaluate(uuf[i][j], omega, eta);
					matrix[i][j] = r;
				}
			}
			matrix = PageRank.normalizeByRow(matrix, start);
			matrix = MyMath.getRidOfEpsilon(matrix);
			matrix = PageRank.normalizeByRow(matrix, start);
			relation.add(matrix);
		}
	}

	/*
	 * A = inv(I - alpha * P')
	 * P' = fw
	 */
	public static double[] deltaOmega(double[][][] uuf, double[][] fw, double[][] A,
			double[][] ucf, int[] start, double[] p, double[] omega,
			double alpha, double beta, double lamda, double eta) {

		int dim = uuf.length;
		
		assert (fw.length == dim && fw[0].length == dim);
		assert (ucf.length == dim);
		assert (p.length == dim);
		assert (uuf[0][0].length == omega.length);
		
		// step1: prepare sum of columns of fw and the squared reciprocal
		double[] sumfw = new double[dim];
		Arrays.fill(sumfw, 0.0);
		for (int i = 0; i < dim; i++) {
			for (int j = 0; j < dim; j++) {
				sumfw[j] += fw[i][j];
			}
		}
		
		for (int j = 0; j < sumfw.length; j++) {
			if (sumfw[j] < PageRank.epsilon) {
				sumfw[j] = PageRank.epsilon;
			}
		}
//		System.out.print("sumfw = ");
//		MyMath.printVector(sumfw);
		
		
		
		double[] sr_sumfw = new double[dim];
		for (int i = 0; i < dim; i++) {
			sr_sumfw[i] = 1 / sumfw[i] / sumfw[i];
		}
		
//		System.out.println("sr_sumfw = ");
//		MyMath.printVector(sr_sumfw);
		
		// step2: prepare sum of feature in each column in each dimension
		double[][] sum_fw_psi = new double[dim][omega.length];
		for (int j = 0; j < dim; j++) {
			for (int k = 0; k < dim; k++) {
				for (int i = 0; i < omega.length; i++) {
					sum_fw_psi[j][i] += uuf[j][k][i] * fw[j][k];
				}
			}
		}
		
		// step3: prepare partial_Q / partial_omega
		double[][][] p_Q_o = new double[dim][dim][omega.length];
		double factor = alpha * eta;
		for (int j = 0; j < dim; j++) {
			for (int u = 0; u < dim; u++) {
				for (int i = 0; i < omega.length; i++) {
					p_Q_o[j][u][i] = factor	* sr_sumfw[j] *
							(uuf[j][u][i] * sumfw[j] - fw[j][u]	* sum_fw_psi[j][i]);
				}
			}
		}
		
		double[][] sample_pq = new double[dim][dim];
		for (int i = 0; i < dim; i++) {
			for (int j = 0; j < dim; j++) {
				sample_pq[i][j] = p_Q_o[i][j][0];
			}
		}
//		System.out.println("partial_Q = ");
//		MyMath.printMatrix(sample_pq);
		
		
		// step4: calculate partial_p / partial_w, iterate until converge
		/*
		double[][] pp = new double[dim][omega.length];
		double[][] prevpp = new double[dim][omega.length];
		int round = 0;
		int ROUND = 100;
		for (int i = 0; i < dim; i++) {
			Arrays.fill(prevpp[i], 1.0);
			Arrays.fill(pp[i], 0.0);
		}
		assert (PageRank.checkMatrixNormalized(fw));
		while (!MyMath.almostEqual(pp, prevpp) && round < ROUND) {
			for (int i = 0; i < dim; i++) {
				prevpp[i] = Arrays.copyOf(pp[i], pp[i].length);
				Arrays.fill(pp[i], 0.0);
			}
			
			for (int j = 0; j < dim; j++) {
				for (int u = 0; u < dim; u++) {
					for (int i = 0; i < omega.length; i++) {
						pp[u][i] += prevpp[j][i] * fw[j][u];
						pp[u][i] += p[j] * p_Q_o[j][u][i];
					}
				}
			}
			round++;
		}
		if (round == ROUND) {
			System.out.println("Partial p may not converge");
			MyMath.printMatrix(pp);
		}
		*/
		
		// step4: calculate partial_p / partial_w
		// 	partial_p						partial_Q'
		//	--------- = alpha * trans(P) * ----------- * A
		//	partial_w						partial_w
		
		double[][] p_partial_Q = new double[dim][omega.length];
		for (int k = 0; k < dim; k++) {
			for (int j = 0; j < dim; j++) {
				for (int i = 0; i < omega.length; i++) {
					p_partial_Q[j][i] += p[k] * p_Q_o[k][j][i];
				}
			}
		}
		for (int j = 0; j < dim; j++) {
			for (int i = 0; i < omega.length; i++) {
				p_partial_Q[j][i] *= alpha;
			}
		}
		
		double[][] pp = new double[dim][omega.length];
		for (int k = 0; k < dim; k++) {
			for (int j = 0; j < dim; j++) {
				for (int i = 0; i < omega.length; i++) {
					pp[j][i] += p_partial_Q[k][i] * A[k][j];
				}
			}
		}
		
//		System.out.println("partial p = ");
//		MyMath.printMatrix(pp);
		
		// step5: calculate delta omega
		double[] deltaOmega = new double[omega.length];
		Arrays.fill(deltaOmega, 0.0);
		for (int k = 0; k < dim; k++) {
			for (int j = 0; j < start.length - 1; j++) {
				for (int i = 0; i < omega.length; i++) {
					deltaOmega[i] += SRW.partial_sigmoid(p[k] - p[start[j]], beta)
							* (pp[k][i] - pp[start[j]][i]);
				}
			}
		}
		
//		MyMath.printVector(deltaOmega);
		
		for (int i = 0; i < omega.length; i++) {
			deltaOmega[i] *= lamda;
			deltaOmega[i] += 2 * omega[i];
		}
		
		return deltaOmega;
	}
}
